package com.beta.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.framework.service.impl.SuperServiceImpl;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.toolkit.CollectionUtil;
import com.beta.dto.order.CreateOrderDto;
import com.beta.dto.order.OrdersDto;
import com.beta.dto.product.ProductItemQuantityDto;
import com.beta.em.order.*;
import com.beta.entity.*;
import com.beta.event.order.OrderEvent;
import com.beta.event.order.OrderPayloadEvent;
import com.beta.mapper.OrdersMapper;
import com.beta.service.*;
import com.beta.util.RandomUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Created by vector on 2016/12/14.
 */
@Service
public class OrderOperateService extends SuperServiceImpl<OrdersMapper, Orders> implements IOrderOperateService {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private IOrdersService ordersService;
    @Autowired
    private IOrderItemsService orderItemsService;
    @Autowired
    private IOrderStatusService orderStatusService;
    @Autowired
    private IProductItemsService productItemsService;
    @Autowired
    private IProductsService productsService;
    @Autowired
    private IStockLocationsService stockLocationsService;
    @Autowired
    private IDateTimeService dateTimeService;
    @Autowired
    private ICouriersService couriersService;
    @Autowired
    private IShipmentsService shipmentsService;
    @Autowired
    private PublishService publishService;
    @Autowired
    private IReceiversService receiversService;
    @Autowired
    private ICitiesService cityService;
    @Autowired
    private IUsersService usersService;
    @Autowired
    private ICartItemsService cartItemsService;

    @Value("${order.expire.minutes}")
    private Long orderExpireMinutes;

    @Override
    public OrdersDto createOrder(Integer userId, CreateOrderDto createOrderDto) {
        logger.info("订单创建 <== userId={}, orderInfo={}", userId, JSON.toJSONString(createOrderDto));

        Integer receiverId = createOrderDto.getReceiverId();
        Receivers receiver = receiversService.selectById(receiverId);
        if (Objects.isNull(receiver)) {
            throw new RuntimeException("E40004");
        }
        List<Integer> cartItemIds = createOrderDto.getCartItemIds();
        if (CollectionUtils.isEmpty(cartItemIds)) {
            throw new RuntimeException("E40007");
        }

        // 创建订单并返回创建的订单对象
        Orders order = doCreateOrder(userId, createOrderDto);

        // 删除购物车
        deleteCartItems(cartItemIds);

        // 创建运单
        createShipment(receiver, order);

        // 分发订单创建应用时间
        publishService.publishApplicationEvent(new OrderPayloadEvent(OrderEvent.CREATE, order));

        return ordersService.getOrder(order.getId());
    }

    @Override
    public void executeCancelOrder(Integer orderId) {
        Orders order = ordersService.selectById(orderId);
        String status = order.getStatus();
        if (status.equals(OrderStatus.INIT.getValue())
                || status.equals(OrderStatus.PENDING.getValue())
                || status.equals(OrderStatus.FREIGHT_PAYING.getValue())
                || status.equals(OrderStatus.PAYING.getValue())) {
            order.setStatus(OrderStatus.CANCELED.getValue());
            order.setUpdatedAt(dateTimeService.toDbDate());
            ordersService.updateSelective(order, new Orders().setId(orderId));
        }
        if (order.getMode().equals(OrderMode.BUYING.getValue()) && order.getPayMode().equals(PayMode.COD.getValue())) {
            order.setStatus(OrderStatus.ASSIGNING.getValue());
            order.setUpdatedAt(dateTimeService.toDbDate());
            ordersService.updateSelective(order, new Orders().setId(orderId));
        }

        // 恢复库存
        List<OrderItems> orderItemsList = orderItemsService.selectList(new EntityWrapper<>(new OrderItems().setOrderId(orderId)));
        restoreProductItemQuantities(orderItemsList);

        // 发布订单取消事件
        publishService.publishApplicationEvent(new OrderPayloadEvent(OrderEvent.CANCEL, ordersService.selectById(orderId)));
    }

    @Override
    public void executePaymentConfirm(Integer orderId) {
        Orders order = ordersService.validateOrder(orderId);

        if (PayMode.COD.getValue().equals(order.getPayMode())) {
            // 配送单支付
            // status:      freight_paying  ==> assigning
            // pay_status:  freight_unpaid  ==> freight_paid
            orderStatusService.changeStatus(orderId, OrderStatus.ASSIGNING, PayStatus.FREIGHT_PAID);
        } else if (PayMode.ONLINE.getValue().equals(order.getPayMode())) {
            // 订单支付
            if (PayStatus.UNCHECKED.getValue().equals(order.getPayStatus())) {
                // status:      unchecked  ==> assigning
                // pay_status:  unchecked  ==> paid
                orderStatusService.changeStatus(orderId, OrderStatus.ASSIGNING, PayStatus.PAID);
            }
            orderStatusService.changeStatus(orderId, OrderStatus.ASSIGNING, PayStatus.PAID);
        }

        // 发布订单支付成功事件
        publishService.publishApplicationEvent(new OrderPayloadEvent(OrderEvent.PAYMENT_SUCCESS, selectById(order.getId())));

        // 订单发货
        executeAssigningOrder(orderId);
    }

    @Override
    public void executeAssigningOrder(Integer orderId) {

        logger.info("订单分配承运人 ==> orderId: {}", orderId);

        Orders order = ordersService.validateOrder(orderId);

        if (!orderStatusService.isPaid(order)) {
            logger.error("分配承运人失败 ==> 订单[{}]还未支付", orderId);
            return;
        }

        if (orderStatusService.isAssigned(order)) {
            logger.error("分配承运人失败 ==> 订单[{}]已经发货", orderId);
            return;
        }

        // 执行分配承运人操作
        doAssigningOrder(order);

        // 修改订单状态
        orderStatusService.changeStatus(orderId, OrderStatus.ASSIGNING, null);

        // 发布指定承运人事件
        publishService.publishApplicationEvent(new OrderPayloadEvent(OrderEvent.ASSIGNING, selectById(orderId)));
    }

    @Override
    public void executeReceiptConfirm(Integer orderId) {
        Orders orders = selectById(orderId);

        if (!orderStatusService.isPicked(orders)) {
            logger.warn("订单确认收货 ==> 执行失败：orderId={},error={}", orderId, "订单未接单");
            return;
        }
        if (orderStatusService.isReceipted(orders)) {
            logger.warn("订单确认收货 ==> 执行失败：orderId={},error={}", orderId, "订单已确认收货");
            return;
        }

        // 修改订单状态
        orderStatusService.changeStatus(orderId, OrderStatus.CONFIRMED, null);

        // 送货完成
        shipmentsService.executeCompleteByOrderId(orderId);

        // 发布确认收货事件
        publishService.publishApplicationEvent(new OrderPayloadEvent(OrderEvent.RECEIPT_CONFIRMED, selectById(orderId)));

        // 订单完成操作
        executeCompleteOrder(orderId);
    }

    private void deleteCartItems(List<Integer> cartItemIds) {
        if (CollectionUtils.isEmpty(cartItemIds)) {
            return ;
        }
        cartItemsService.deleteBatchIds(cartItemIds);
    }

    private Orders doCreateOrder(Integer userId, CreateOrderDto createOrderDto) {
        List<Integer> cartItemIds = createOrderDto.getCartItemIds();
        if (CollectionUtils.isEmpty(cartItemIds)) {
            throw new ServiceException("E30002");
        }
        List<CartItems> cartItems = cartItemsService.selectBatchIds(cartItemIds);
        if (CollectionUtils.isEmpty(cartItems)) {
            throw new ServiceException("E30002");
        }

        List<Integer> productItemIds = cartItems.stream().map(CartItems::getProductItemId).collect(Collectors.toList());
        List<ProductItems> productItems = productItemsService.selectBatchIds(productItemIds);
        Map<Integer, ProductItems> productItemMap = productItems.stream().collect(Collectors.toMap(ProductItems::getId, Function.identity()));

        List<Integer> productIds = productItems.stream().map(ProductItems::getProductId).collect(Collectors.toList());
        List<Products> products = productsService.selectBatchIds(productIds);
        Map<Integer, Products> productMap = products.stream().collect(Collectors.toMap(Products::getId, Function.identity()));

        Users user = usersService.selectById(userId);

        // 创建订单
        Orders order = doCreateOrder(user, cartItems, productItemMap, productMap, createOrderDto.getOrderMode(), createOrderDto.getPayMode());

        // 创建订单项
        doCreateOrderItems(order, cartItems, productItemMap, productMap);

        // 扣除库存
        List<ProductItemQuantityDto> productQuantities = cartItems.stream()
                .map(cartItem -> new ProductItemQuantityDto(cartItem.getProductItemId(), cartItem.getQuantity()))
                .collect(Collectors.toList());
        productItemsService.reduceProductItemQuantityBatch(productQuantities);

        return order;
    }

    private Orders doCreateOrder(Users user, List<CartItems> cartItems, Map<Integer, ProductItems> productItemsMap,
                                 Map<Integer, Products> productsMap, OrderMode orderMode, PayMode payMode) {
        Assert.notEmpty(cartItems, "cart items can't be empty!");
        Assert.notEmpty(productItemsMap, "product items can't be empty!");

        Date dbDate = dateTimeService.toDbDate();
        String orderCode = generateOrderCode(user);
        Orders orderForInsert = new Orders()
                .setUserId(user.getId())
                .setCityCode(user.getCityCode())
                .setCityName(user.getCityName())
                .setCode(orderCode)
                .setMode(orderMode.getValue())
                .setPayMode(payMode.getValue())
                .setCustomerManagerCode(user.getCustomerManagerCode())
                .setCustomerManagerName(user.getCustomerManagerName())
                .setCreatedAt(dbDate)
                .setUpdatedAt(dbDate)
                .setExpiredAt(Date.from(dbDate.toInstant().plus(Duration.ofMinutes(orderExpireMinutes))));

        setOrderStatus(orderForInsert, orderMode, payMode);

        calcAndSetOrderAmount(cartItems, productItemsMap, productsMap, orderForInsert);

        insertSelective(orderForInsert);

        return selectOne(new Orders().setCode(orderCode));
    }

    private void setOrderStatus(Orders orders, OrderMode orderMode, PayMode payMode) {
        orders.setStatus(OrderStatus.INIT.getValue());
        orders.setPayStatus(PayStatus.PAID_INIT.getValue());
        if (OrderMode.BUYING.equals(orderMode)) {
            if (PayMode.ONLINE.equals(payMode)) {
                orders.setStatus(OrderStatus.PAYING.getValue());
                orders.setPayStatus(PayStatus.UNPAID.getValue());
            }
            if (PayMode.COD.equals(payMode)) {
                orders.setStatus(OrderStatus.ASSIGNING.getValue());
            }
        } else if (OrderMode.CARTING.equals(orderMode)) {
            orders.setStatus(OrderStatus.PENDING.getValue());
        }
    }

    private void calcAndSetOrderAmount(List<CartItems> cartItems,
                                       Map<Integer, ProductItems> productItemsMap,
                                       Map<Integer, Products> productsMap, Orders orders) {
        List<Integer> stockLocationIds = productItemsMap.values().stream()
                .map(ProductItems::getStockLocationId)
                .collect(Collectors.toList());
        List<StockLocations> stockLocations = stockLocationsService.selectBatchIds(stockLocationIds);
        Map<Integer, StockLocations> stockLocationsMap = stockLocations.stream()
                .collect(Collectors.toMap(StockLocations::getId, Function.identity()));

        BigDecimal price = BigDecimal.ZERO;
        BigDecimal freight = BigDecimal.ZERO;
        for (CartItems cartItem : cartItems) {
            ProductItems productItem = productItemsMap.get(cartItem.getProductItemId());
            Products product = productsMap.get(productItem.getProductId());
            BigDecimal productQuantity = BigDecimal.valueOf(cartItem.getQuantity());
            StockLocations stockLocation = stockLocationsMap.get(productItem.getStockLocationId());

            BigDecimal cartItemPrice = productItem.getPrice().multiply(productQuantity);
            price = price.add(cartItemPrice);
            BigDecimal cartItemFreight = productQuantity.multiply(product.getUnitWeight()).multiply(stockLocation.getUnitFreight());
            freight = freight.add(cartItemFreight);
        }

        BigDecimal amount = price.add(freight);
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("order.amount.zero");
        }
        orders.setPrice(price)
                .setFreight(freight)
                .setAmount(amount);
    }

    private void doCreateOrderItems(Orders order, List<CartItems> cartItems,
                                    Map<Integer, ProductItems> productItemsMap,
                                    Map<Integer, Products> productsMap) {
        Assert.notEmpty(cartItems, "cart items can't be empty!");
        Assert.notEmpty(productItemsMap, "product items can't be empty!");

        Date dbDate = dateTimeService.toDbDate();
        Map<Integer, String> productItemOptionsTextMap = productItemsService.getProductItemOptionsTextBatch(new ArrayList<>(productItemsMap.keySet()));
        Map<Integer, Users> productProviderMap = getProductProviderMap(productItemsMap.values());
        cartItems.forEach(cartItem -> {
            ProductItems productItem = productItemsMap.get(cartItem.getProductItemId());
            Users productProvider = productProviderMap.get(productItem.getId());
            Products product = productsMap.get(productItem.getProductId());
            Integer quantity = cartItem.getQuantity();

            OrderItems orderItem = new OrderItems()
                    .setOrderId(order.getId())
                    .setProductItemId(cartItem.getProductItemId())
                    .setProductItemName(product.getName())
                    .setProductItemSku(productItem.getSku())
                    .setProductItemText(productItemOptionsTextMap.get(productItem.getId()))
                    .setQuantity(quantity)
                    .setPricePerUnit(productItem.getPrice())
                    .setPrice(productItem.getPrice().multiply(new BigDecimal(quantity)))
                    .setCustomerManagerCode(productProvider.getCustomerManagerCode())
                    .setCustomerManagerName(productProvider.getCustomerManagerName())
                    .setCreatedAt(dbDate)
                    .setUpdatedAt(dbDate)
                    .setStatus(OrderItemStatus.ACCEPTED.getValue())
                    .setRefundStatus(RefundStatus.INIT.getValue())
                    .setRefundRate(BigDecimal.ZERO);
            orderItemsService.insertSelective(orderItem);
        });
    }

    private Map<Integer, Users> getProductProviderMap(Collection<ProductItems> productItems) {
        if (CollectionUtil.isEmpty(productItems)) {
            return Collections.emptyMap();
        }

        List<Integer> providerIds = productItems.stream().map(ProductItems::getUserId).collect(Collectors.toList());
        Map<Integer, Users> usersMap = usersService.getUsersBatch(providerIds);
        return productItems.stream().collect(Collectors.toMap(ProductItems::getId, productItem -> usersMap.get(productItem.getUserId())));
    }

    private void createShipment(Receivers receiver, Orders order) {
        Shipments shipment = new Shipments();
        shipment.setCode(shipmentsService.generateShipmentCode());
        shipment.setStatus(order.getStatus()); //以后订单状态改变订单中的status可能会改
        shipment.setOrderId(order.getId());
        shipment.setReceiverId(receiver.getId());
        shipment.setReceiverName(receiver.getUserName());
        shipment.setReceiverAddress(receiver.getAddress());
        shipment.setReceiverPhone(receiver.getUserPhone());
        Date dbDateNow = dateTimeService.toDbDate();
        shipment.setCreatedAt(dbDateNow);
        shipment.setUpdatedAt(dbDateNow);
        shipment.setCityName(order.getCityName());
        shipment.setCityCode(order.getCityCode());

        shipmentsService.insertSelective(shipment);
    }

    /**
     * "#{city_code}#{date_str}#{rand_str}#{count_str}"
     * city_code: currentUser[city][zip_code][0,3] 3位
     * date_str: ymd 6位
     * rand_str: 3位
     * count_str: 4位
     *
     * @param currentUser return "#{city_code}#{date_str}#{rand_str}#{count_str}"
     */
    private String generateOrderCode(Users currentUser) {
        Cities city = cityService.selectOne(new Cities().setCode(currentUser.getCityCode()));
        if (Objects.isNull(city)) {
            throw new ServiceException("E00011", currentUser.getCityCode());
        }

        DateFormat dateFormat = new SimpleDateFormat("yyMMdd");
        String yyMMdd = dateFormat.format(dateTimeService.toDbDate());
        String rand3String = RandomUtils.randomStringWithLength(3);
        int count = baseMapper.selectTodayOrderCount();
        return String.format("%s%s%s%04d", StringUtils.left(city.getZipCode(), 3), yyMMdd, rand3String, count);
    }

    @Override
    public void executeCompleteOrder(Integer orderId) {
        Orders orders = ordersService.validateOrder(orderId);

        // 未确认收货
        if (!OrderStatus.CONFIRMED.getValue().equals(orders.getStatus())) {
            return;
        }

        // 存在退货的订单，并且未确认，不完成订单
        if (hasUnConfirmedRejectOrderItems(orderId)) {
            return;
        }

        // 配送单情况
        if (OrderMode.CARTING.getValue().equals(orders.getMode()) && PayStatus.FREIGHT_PAID.getValue().equals(orders.getPayStatus())) {
            orderStatusService.changeStatus(orderId, null, PayStatus.PAID);
        }

        // 修改状态
        orderStatusService.changeStatus(orderId, OrderStatus.COMPLETED, null);

        // 发布订单完成事件
        publishService.publishApplicationEvent(new OrderPayloadEvent(OrderEvent.COMPLETED, selectById(orderId)));
    }

    private boolean hasUnConfirmedRejectOrderItems(Integer orderId) {
        List<OrderItems> orderItems = orderItemsService.getOrderItemsByOrderId(orderId);
        if (CollectionUtils.isEmpty(orderItems)) {
            return false;
        }
        for (OrderItems orderItem : orderItems) {
            if (OrderItemStatus.REJECTED.getValue().equals(orderItem.getStatus())
                    && RefundStatus.PENDING.getValue().equals(orderItem.getRefundStatus())) {
                return true;
            }
        }
        return false;
    }

    private void doAssigningOrder(Orders order) {
        String cityCode = order.getCityCode();
        Couriers couriers = couriersService.pickCourier(cityCode);
        if (Objects.isNull(couriers)) {
            logger.error("分配承运人失败 ==> 城市[{}-{}]承运人分配失败", order.getCode(), order.getCityName());
            throw new ServiceException("E40008", order.getCityName());
        }

        logger.info("分配承运人 ==> order:{} --> courier:{}", order.getId(), couriers.getId());
        Date dbDateNow = dateTimeService.toDbDate();
        Shipments shipmentsForUpdate = new Shipments()
                .setCourierId(couriers.getId())
                .setCourierName(couriers.getName())
                .setCreatedAt(dbDateNow)
                .setUpdatedAt(dbDateNow)
                .setStatus(ShipmentStatus.UNPICKED.getValue());
        shipmentsService.updateSelective(shipmentsForUpdate, new Shipments().setOrderId(order.getId()));
    }

    private void restoreProductItemQuantities(List<OrderItems> orderItemsList) {
        if (CollectionUtils.isEmpty(orderItemsList)) {
            return;
        }
        List<ProductItemQuantityDto> productItemQuantityDtos = orderItemsList.stream().map(
                orderItem -> new ProductItemQuantityDto(
                        orderItem.getProductItemId(),
                        orderItem.getQuantity()
                )).collect(Collectors.toList());
        productItemsService.restoreProductItemQuantityBatch(productItemQuantityDtos);
    }

}
